Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 <<   zurück
Visual Basic 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual Basic 2005

Visual Basic 2005
1.233 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-585-1
gp Kapitel 6 Vererbung, Polymorphie und Schnittstellen
  gp 6.1 Basisklassen und abgeleitete Klassen
    gp 6.1.1 Die Ableitung einer Klasse
    gp 6.1.2 Klassen, die nicht vererben können
    gp 6.1.3 Zusammenfassung
  gp 6.2 Konstruktoren in abgeleiteten Klassen
    gp 6.2.1 Allgemeines
    gp 6.2.2 Die Konstruktoren der Klasse »GraphicCircle«
    gp 6.2.3 Der Zugriffsmodifizierer »Protected«
    gp 6.2.4 Konstruktorverkettung
    gp 6.2.5 Finalizer-Verkettung
    gp 6.2.6 Zusammenfassung
    gp 6.2.7 Der Stand des Projekts »CircleApplication«
  gp 6.3 Die Methoden in einer abgeleiteten Klasse
    gp 6.3.1 Geerbte Methoden mit »Shadows« verdecken
    gp 6.3.2 Überladen einer Basisklassenmethode
  gp 6.4 Ereignisse in der Vererbung
  gp 6.5 »Hat-eine«-Beziehungen (Aggregation)
    gp 6.5.1 Weiterleitung einer internen Objektreferenz
    gp 6.5.2 Verbergen des internen Objekts
    gp 6.5.3 Innere Klassen
  gp 6.6 Typumwandlung von Objektvariablen
    gp 6.6.1 Die implizite Typumwandlung von Objektreferenzen
    gp 6.6.2 Die explizite Typumwandlung von Objektreferenzen
    gp 6.6.3 Zusammenfassung
  gp 6.7 Abstrakte Klassen und Methoden
    gp 6.7.1 Problembeschreibung
    gp 6.7.2 Abstrakte Definitionen
  gp 6.8 Polymorphie
    gp 6.8.1 Überschreibbare Methoden mit »Overridable«
    gp 6.8.2 Inhomogene Mengen
    gp 6.8.3 Verdecken und Überschreiben geerbter Methoden
    gp 6.8.4 Überschreiben der Methode »ToString()« der Klasse »Object«
    gp 6.8.5 Nicht überschreibbare Methoden
    gp 6.8.6 Die Referenzen »Me«, »MyBase« und »MyClass«
    gp 6.8.7 Zusammenfassung
  gp 6.9 Erweiterung der Klassenhierarchie »CircleApplication«
    gp 6.9.1 Die Klasse »GeometricObject«
  gp 6.10 Schnittstellen
    gp 6.10.1 Einführung in die Schnittstellen
    gp 6.10.2 Schnittstellendefinition
    gp 6.10.3 Schnittstellenimplementierung
    gp 6.10.4 Abstrakte Klassen vs. Schnittstellen
    gp 6.10.5 Zusammenfassung


Galileo Computing

6.10 Schnittstellen  downtop


Galileo Computing

6.10.1 Einführung in die Schnittstellen  downtop

Das Konzept der Schnittstellen ist am einfachsten zu verstehen, wenn man sich deutlich macht, worin genau der Unterschied zwischen einer Klasse und einem Objekt besteht. Klassen sind Schablonen, in denen Methoden und Eigenschaften definiert sind. Die Methoden manipulieren die Eigenschaften und stellen damit das Verhalten eines Objekts sicher. Ein Objekt wird jedoch nicht durch sein Verhalten, sondern durch seine Daten beschrieben, die über Eigenschaften manipuliert werden.

Treiben wir die Abstraktion noch weiter. Wenn sich ein Objekt durch Daten beschreibt und in einer Klassendefinition Eigenschaften und Methoden definiert sind, dann muss es auch ein Extrem geben, das nur Verhaltensweisen festlegt: Und genau das sind die Schnittstellen.

Die Aufgabe der Schnittstellen geht über die einfache Fähigkeit, Verhaltensweisen bereitzustellen, hinaus. Bekanntlich unterstützt die Common Language Runtime keine Mehrfachvererbung. Damit sind die .NET-Architekten möglichen Schwierigkeiten aus dem Weg gegangen, die mit der Mehrfachvererbung verbunden sind. Mehrfachvererbung ist nur schwer zu realisieren und wird deshalb in der Praxis auch nur selten eingesetzt. Andererseits hielt man es als für erstrebenswert, neben der Basisklasse weitere »Oberbegriffe« zuzulassen, um gemeinsame Merkmale mehrerer ansonsten unabhängiger Klassen beschreiben zu können. Mit der Schnittstelle wurde ein Konstrukt geschaffen, das genau diese Möglichkeiten bietet.

Sie müssen sich Schnittstellen wie eine Vertragsvereinbarung vorstellen. Sobald eine Klasse eine Schnittstelle implementiert, haben alle auf die Klasse zugreifenden Clients die Garantie, dass die Klasse alle Verhaltensdefinitionen der Schnittstelle veröffentlicht. Mit anderen Worten: Eine Schnittstelle legt einen Vertragsrahmen fest, den die implementierende Klasse erfüllen muss.


Galileo Computing

6.10.2 Schnittstellendefinition  downtop

Schnittstellen können Methoden, Eigenschaften und Ereignisse bereitstellen. Das Besondere an einer Schnittstelle ist, dass sie selbst keine Codeimplementierung enthält, sondern ausnahmslos nur abstrakte Definitionen. Schauen wir uns dazu ein einfaches Beispiel an:


Public Interface ILuftfahrzeug
Property Hersteller() As String
Sub Starten()
Sub Landen()
End Interface

Die Definition einer Schnittstelle ähnelt der Definition einer Klasse, bei der das Schlüsselwort Class gegen das Schlüsselwort Interface ausgetauscht worden ist. Fehlt die Angabe eines Zugriffsmodifizierers, gilt eine Schnittstelle standardmäßig als Friend, ansonsten bietet sich noch Public an. Deklarieren Sie eine Schnittstelle innerhalb eines Typs (also einer Klasse), kommen noch Protected und Private hinzu. In der Praxis sind innere Interfaces aber bedeutungslos. Hinter der Definition werden in geschweiften Klammern alle Mitglieder der Schnittstelle aufgeführt, die durch ein Semikolon voneinander getrennt werden. Beachten Sie, dass das von den abstrakten Klassen her bekannte Schlüsselwort MustOverride in einer Schnittstellendefinition nicht auftaucht.


Hinweis

Konventionsgemäß wird dem Bezeichner einer Schnittstelle ein »I« vorangestellt.


Die Schnittstelle ILuftfahrzeug implementiert die Eigenschaft Hersteller sowie die Methoden Starten und Landen. Weil eine Schnittstelle grundsätzlich nur abstrakte Definitionen bereitstellt, enthält kein Mitglied einen Anweisungsblock. Es ist auch kein Zugriffsmodifizierer angegeben, weil eine Schnittstellendefinition das nicht erlaubt. Der Compiler würde mit einer Fehlermeldung reagieren, wenn Sie einem Schnittstellenmitglied einen Zugriffsmodifizierer voranstellen, denn die Festlegung eines Zugriffsmodifizierers ist die Aufgabe der implementierenden Klasse.

Schnittstellen definieren einzelne oder auch eine Gruppe zusammengehöriger Member. Damit wird eine Spezifikation beschrieben, an die sich die implementierende Klasse halten muss: Sie verpflichtet sich, alle Elemente der Schnittstelle zu übernehmen. Auf eine Schnittstelle sind Sie bereits in Kapitel 4 gestoßen: Es war IDisposable. Klassen, die dieses Interface implementieren, garantieren damit, eine Methode Dispose bereitzustellen.


Galileo Computing

6.10.3 Schnittstellenimplementierung  downtop

Bei der Vererbung wird von »Ableitung« gesprochen, analog wurde bei den Schnittstellen der Begriff »Implementierung« geprägt. Eine Schnittstelle ist wie ein Vertrag, den eine Klasse unterschreibt, sobald sie eine bestimmte Schnittstelle implementiert. Das hat Konsequenzen: Eine Klasse, die eine Schnittstelle implementiert, muss ausnahmslos jedes Mitglied der Schnittstelle übernehmen.

Eine zu implementierende Schnittstelle wird, getrennt durch das Schlüsselwort Implements, hinter dem Klassenbezeichner angegeben:


Class Hubschrauber
Implements ILuftfahrzeug

Eine Klasse ist nicht nur auf die Implementierung einer Schnittstelle beschränkt, es dürfen – im Gegensatz zur Vererbung – auch mehrere sein. Wird die Klasse außerdem noch aus einer anderen Klasse abgeleitet oder implementiert die Klasse mehrere Schnittstellen, werden alle Typbezeichner durch ein Komma getrennt aufgelistet:


Class Hubschrauber
Implements ILuftfahrzeug, IDisposable

Schnittstellen dürfen nach der Veröffentlichung nicht mehr verändert werden, da sowohl der Client als auch die implementierende Klasse in einem Vertragsverhältnis zueinander stehen und die Bedingungen des Vertrags erfüllt werden müssen. Im wirklichen Leben ist das auch nicht anders. Mit der Veröffentlichung einer Schnittstelle erklärt sich eine Klasse bereit, die Schnittstelle exakt so zu implementieren, wie sie entworfen wurde. Die von der Klasse übernommenen Mitglieder der Schnittstelle müssen daher in jeder Hinsicht identisch zu ihrer Definition sein:

1. Der Name muss dem in der Schnittstelle entsprechen.
       
2. Rückgabewert und Parameterliste dürfen nicht von denen in der Schnittstellendefinition abweichen.
       

Ein aus einer Schnittstelle übernommenes Mitglied darf nur Public sein. Es ist zulässig, in einer Klasse ein Schnittstellenmitglied MustOverride oder Overridable zu implementieren, es darf jedoch nicht Shared oder Const sein.

Das folgende Codefragment zeigt die Klasse Hubschrauber, welche die oben definierte Schnittstelle ILuftfahrzeug implementiert und konventionsgemäß alle Schnittstellenmitglieder veröffentlicht:


Class Hubi
Implements ILuftfahrzeug
Public Property Hersteller() As String _
Implements ILuftfahrzeug.Hersteller
Get
End Get
Set(ByVal value As String)
End Set
End Property
Public Sub Landen() Implements ILuftfahrzeug.Landen
End Sub
Public Sub Starten() Implements ILuftfahrzeug.Starten
End Sub
End Class

Der Name der implementierten Methode muss dem der Schnittstelle entsprechen. Darüber hinaus muss hinter der Methodensignatur das Implements-Statement zusammen mit den Namen des Interfaces und dem Methodenbezeichner als Ergänzung angegeben werden.

Schnittstellen und Vererbung

Vererbung ist eines der Kernkonzepte objektorientierter Systeme. Eine Klasse, die aus einer anderen Klasse abgeleitet wird, erbt alle Methoden der Basisklasse. Wir wissen auch, dass Ereignisse aus der Basisklasse nicht an die abgeleitete Klasse vererbt werden. Daher stellt sich die Frage, ob ein Interface gleichermaßen ein nicht vererbbares Feature darstellt oder die abgeleitete Klasse die aus einer Schnittstelle übernommenen Methoden der Basisklasse veröffentlicht. Wir wollen das an einem kleinen Beispiel prüfen.


Module Module1
Sub Main()
Dim obj As New ClassB
obj.Proc()
Console.ReadLine()
End Sub
End Module
Interface IMyInterface
Sub Proc()
End Interface
Class ClassA
Implements IMyInterface
Public Sub Proc() Implements IMyInterface.Proc
Console.WriteLine("Proc in ClassA")
End Sub
End Class
Class ClassB
Inherits ClassA
End Class

Die Schnittstelle IMyInterface definiert die Methode Proc, die von jeder implementierenden Klasse übernommen werden muss – im Code oben ist es die Klasse ClassA. ClassB wird aus ClassA abgeleitet. Wenn Sie dieses sehr einfache Programm starten, wird es fehlerfrei ausgeführt und beweist damit, dass auch die Schnittstellenmethoden vererbt werden.

Hinsichtlich einer Schnittstelle zeigt eine Methode polymorphes Verhalten. Das setzt sich jedoch nicht bei den ableitenden Klassen durch. Eine ableitende Klasse kann daher die implementierte Methode nur erben oder mit Shadows verdecken. Soll sich die Methode auch in den ableitenden Klassen polymorph verhalten, muss sie mit dem Modifizierer Overridable signiert werden.

Mehrdeutigkeiten vermeiden

Es ist nicht weit hergeholt, wenn man davon ausgeht, dass mehrere von einer Klasse zu implementierende Schnittstellen über gleichnamige Methoden verfügen könnten. Diese Mehrdeutigkeiten würden zu Inkonsistenzen führen und folglich einen Fehler verursachen. Das Problem wird durch eine explizite Kennzeichnung der Methodendeklaration mit dem Schlüsselwort Implements aus der Welt geschafft, dem der Name der Schnittstelle und der Methodenname folgt.

Der Vorteil ist, dass die von einer Klasse veröffentlichte Methode nicht den Namen der in der Schnittstelle deklarierten Methode haben muss, sondern nach eigenem Ermessen im Rahmen der üblichen Regeln für Bezeichner gewählt werden darf, beispielsweise:


Public Class Flugzeug
Implements ILuftfahrzeug
Sub Startvorgang() Implements ILuftfahrzeug.Starten
Console.WriteLine("Das Flugzeug startet")
End Sub
Sub Landung() Implements ILuftfahrzeug.Landen
Console.WriteLine("Das Flugzeug landet")
End Sub
End Class

Um auf die über eine Schnittstelle implementierte Methode einer Klasse zuzugreifen, wird in bekannter Art per Punktnotation die Methode auf das Objekt ausgeführt.


Dim flzg As New Flugzeug
flzg.Startvorgang()

Schnittstellen, die selbst Schnittstellen implementieren

Mehrere Schnittstellen können zu einer neuen Schnittstelle zusammengefasst werden. Das folgende Codefragment zeigt, wie die Schnittstelle ICollect die beiden Schnittstellen INewInterface1 und INewInterface2 implementiert.


Interface INewInterface1
Sub Proc1()
End Interface
Interface INewInterface2
Sub Proc2()
End Interface
Interface ICollect
Inherits INewInterface1, INewInterface2
Sub Proc3()
End Interface

Beachten Sie, dass die Ableitung einer Schnittstelle in eine andere nicht mit dem Schlüsselwort Implements, sondern mit Inherits erfolgt. Ausnahmsweise dürfen sogar mehrere Schnittstellen beerbt werden, die durch ein Komma voneinander getrennt werden. Die geerbten Schnittstellenmitglieder werden in der Subschnittstelle nicht aufgeführt.

Hat eine Klasse eine bestimmte Schnittstelle implementiert?

In der täglichen Programmierpraxis werden Sie immer wieder auf dieselben Schwierigkeiten stoßen und Lösungen entwerfen müssen. Eine dieser aufgeworfenen Fragen wird lauten: Wie kann ich feststellen, ob der Typ eines Objekts eine bestimmte Schnittstelle implementiert?

Betrachten wir dazu ein einfaches Beispiel und stellen uns vor, dass die beiden Schnittstellen IMyInterface1 und IMyInterface2 folgendermaßen definiert sind:


' ---------------------------------------------------------------
' Beispiel: ...\Kapitel 6\Schnittstellenprüfung
' ---------------------------------------------------------------
Interface IMyInterface1
Sub MyProc1()
End Interface
Interface IMyInterface2
Sub MyProc2()
End Interface

Die beiden Klassen ClassA und ClassB implementieren beide IMyInterface2, ClassA zusätzlich noch IMyInterface1.


' Definition der ClassA
Public Class ClassA
Implements IMyInterface1, IMyInterface2
Public Sub MyProc1() Implements IMyInterface1.MyProc1
Console.WriteLine("ClassA.MyProc1")
End Sub
Public Sub MyProc2() Implements IMyInterface2.MyProc2
Console.WriteLine("ClassA.MyProc2")
End Sub
End Class
'Definition der ClassB
Public Class ClassB
Implements IMyInterface2
Public Sub MyProc2() Implements IMyInterface2.MyProc2
Console.WriteLine("ClassB.MyProc2")
End Sub
End Class

Die Codeimplementierung ist wieder sehr einfach gehalten, um das Verständnis der Abläufe auf das Wesentliche zu konzentrieren. Bis zu dieser Stelle entspricht alles dem bisher Angesprochenen.

Schwierigkeiten im zugreifenden Programmcode können prinzipiell dann auftreten, wenn in einem Array Referenzen auf Objekte von mehreren Typen verwaltet werden. Die Intention, Objektreferenzen in einem Array zu verwalten, beruht meistens auf dem Wunsch, in einer Schleife alle oder einen bestimmten Teil der Elemente zu durchlaufen und auf diese Objekte dieselben Methoden auszuführen.

Angenommen, ein Array würde die beiden Klassen ClassA und ClassB verwalten und es soll die Methode MyProc1 der Schnittstelle IMyInterface1 auf alle Objekte ausgeführt werden, die diese Schnittstelle veröffentlichen. Gemäß der Definition der beiden Klassen weiter oben kann es sich dabei nur um die Objekte vom Typ der ClassA handeln. Wird das Array in einer Schleife vom ersten bis zum letzten Element in einer Schleife durchlaufen, verursacht der Aufruf der Methode MyProc1 auf ein Objekt vom Typ der ClassB einen Fehler.

Um dies zu vermeiden, muss zuerst die Referenz daraufhin geprüft werden, ob der von der Referenz beschriebene Typ die Schnittstelle IMyInterface1 implementiert. Dazu braucht man eine Methode, um das Objekt nach seinen Fähigkeiten zu befragen. Der Operator TypeOf ... Is, der meist im Kontext der konditionalen If ... Then ... Else-Anweisung eingesetzt wird, erfüllt diese Aufgabe:


If TypeOf <Objektreferenz> Is <Typ> Then ...

Dem ersten Operanden wird eine gültige Objektreferenz übergeben, dem zweiten ein Typname. Bezogen auf die Problematik, feststellen zu wollen, ob der Typ eines Objekts eine gegebene Schnittstelle implementiert, wird im zweiten Operanden die gesuchte Schnittstelle angegeben. Der Rückgabewert ist True, wenn der Objekttyp die Schnittstelle unterstützt.

In der folgenden Main-Prozedur wird ein sechselementiger Array vom Typ Object deklariert. In einer Schleife, die vom ersten bis zum letzten Index des Arrays durchlaufen wird, werden alle Referenzen initialisiert. Die überladene Methode Next der Klasse Random, die uns mit den beiden Argumenten 0 und 2 nur die Zufallszahlen 0 und 1 liefert, bestimmt dabei für jedes Element zufällig den konkreten Typ. Liefert die Methode Next eine 0, wird die Klasse ClassA instanziiert, mit dem Rückgabewert 1 ein Objekt des Typs ClassB.


Sub Main()
Dim obj(5) As Object
Dim rnd As New Random()
' Array-Elemente initialisieren
For i As Integer = 0 To obj.GetUpperBound(0)
If rnd.Next(0, 2) = 0 Then
obj(i) = New ClassA()
Else
obj(i) = New ClassB()
End If
Next
' Ausgabe an der Konsole
For i = 0 To obj.GetUpperBound(0)
If TypeOf obj(i) Is IMyInterface1 Then
obj(i).MyProc1()
obj(i).MyProc2()
Else
obj(i).MyProc2()
End If
Console.WriteLine(obj(i).GetType)
Console.WriteLine("--------------------------------")
Next
Console.ReadLine()
End Sub

In einer zweiten Schleife wird mit TypeOf ... Is geprüft, ob der Typ der aktuellen Referenz die Schnittstelle IMyInterface1 implementiert, und im positiven Fall die Methode MyProc1 aufgerufen. Die Ausgabe könnte beispielsweise wie folgt lauten:


ClassB.MyProc2
ConsoleApplication1.ClassB
---------------------------------------------
ClassA.MyProc1
ClassA.MyProc2
ConsoleApplication1.ClassA
---------------------------------------------
...

Auch hier spielt wieder die Polymorphie eine entscheidende Rolle, um die zu einem bestimmten Objekt gehörende Methode aufzurufen.


Galileo Computing

6.10.4 Abstrakte Klassen vs. Schnittstellen  downtop

Die Notwendigkeit bzw. der Vorteil der Schnittstellenimplementierung ist am Anfang nicht einfach zu verstehen. Bestimmt werden Sie erkannt haben, dass die Schnittstellenimplementierung als eine Ergänzung des Vererbungskonzepts angesehen werden kann. Die Beispiele waren bisher alle sehr einfach gehalten, nun wollen wir uns einem komplexeren Beispiel zuwenden und dabei die Lösung für ein Problem erarbeiten, das zuerst durch eine abstrakte Klasse beschrieben wird und im zweiten Ansatz durch eine Schnittstelle.

Problembeschreibung

Stellen Sie sich vor, Sie möchten einen Algorithmus implementieren, der die Objekte eines Arrays der Größe nach auf- bzw. absteigend sortiert. Der Algorithmus soll sich dabei nur auf bestimmte .NET-Typen beschränken. Eine leichte Aufgabe, werden Sie jetzt vermutlich denken. Aber aus der Einschränkung auf bestimmte Typen resultiert eine verhältnismäßig komplizierte Lösung.

Die Sortierroutine

Am Anfang steht die Überlegung, die Sortierroutine mit dem Bezeichner SortElements in einer eigenen Klasse zu implementieren. Der erste Ansatz könnte dann wie folgt aussehen:


Public Class ArraySort
Public Shared Sub SortElements(ByRef arr() As IrgendEinTyp)
' Anweisungen
End Sub
End Class

Der Rückgabewert von SortElements ist void, folglich wird das sortierte Array über den Parameter an den Aufrufer zurückgegeben. Der Implementierung werden wir uns gleich widmen, denn zuerst müssen wir uns Gedanken darüber machen, von welchem Typ das übergebene Array sein soll. Im Codefragment ist der Typ noch mit IrgendEinTyp angegeben.

Um die Sortierung auf bestimmte Typen einzuschränken, müssen wir diesen exakt festlegen. Dazu definieren wir eine zweite Klasse, die später als Basisklasse von den Klassen abgeleitet werden muss, deren Instanzen von SortElements sortiert werden sollen. Wir legen damit den Typ des übergebenen Arrays fest, denn nach den Regeln der Objektorientierung gilt, dass ein Objekt einer abgeleiteten Klasse auch gleichzeitig vom Typ seiner Basisklasse ist.


Public Class SortableObject
' Anweisungen
End Class

Nun können wir den Methodenkopf von SortElements anpassen:


Public Shared Sub SortElements(ByRef arr() As SortableObject)

Damit genügen wir der Forderung, nur bestimmte .NET-Typen sortieren zu können. Unabhängig davon, ob ein Array vom Typ Person, Elefant oder ClassA übergeben wird, wird der Parameter das Array in Empfang nehmen – vorausgesetzt natürlich, dass die Klassen von SortableObject abgeleitet sind. Widmen wir uns nun der Realisierung der Methode SortElements. Es gibt verschiedene Algorithmen, um Elemente zu sortieren: Bubblesort, Quicksort, Insertionsort – um nur einige zu nennen. Die Bevorzugung eines dieser Sortierverfahren hängt vom Umfang der Daten und vom durchzuführenden Vergleich ab.

Für unser Beispiel habe ich mich für das Bubblesort-Verfahren entschieden. Der Name rührt wohl daher, dass sich die Funktionsweise sehr gut mit den aufsteigenden Luftblasen in einer Flüssigkeit vergleichen lässt. Die Elemente eines Arrays werden in aufsteigender Richtung durchlaufen, und dabei werden immer zwei benachbarte Elemente verglichen. Angenommen, ein Array namens MyArr mit vier Elementen soll der Größe nach sortiert werden, würden nacheinander die Elementpaare


MyArr(0) – MyArr(1)
MyArr(1) – MyArr(2)
MyArr(2) – MyArr(3)

verglichen und jedes Paar in die richtige Reihenfolge gebracht. Wenn das Array aufsteigend sortiert werden soll, muss das zweite Element größer als das erste sein. Die Folge ist nach diesen drei Vergleichen, dass das höchstwertige Element – selbst wenn es sich im ursprünglichen Array ganz am Anfang befindet – bis an das Ende des Arrays (MyArr(3)) durchgereicht wird. Die Anzahl der Paarvergleiche entspricht der Bedingung

Anzahl der Array-Elemente – 1

Dieser Durchlauf wird wiederholt, wobei das bereits richtig einsortierte Element keine Berücksichtigung mehr findet:


MyArr(0) – MyArr(1)
MyArr(1) – MyArr(2)

Nach dem zweiten Durchlauf befindet sich das Element mit dem zweithöchsten Wert an der vorletzten Array-Position. Die Paarvergleiche werden so lange fortgesetzt, bis der Algorithmus mit dem letzten Paarvergleich


MyArr(0) – MyArr(1)

beendet wird.

Das Bubblesort-Sortierverfahren lässt sich am einfachsten mit zwei Schleifen wie folgt implementieren:

1. Eine äußere Schleife mit einer Anzahl von Schleifendurchläufen, die der Bedingung Anzahl der Array-Elemente – 1 genügt.
       
2. Eine innere Schleife, die den Paarvergleich durchführt und gegebenenfalls die Reihenfolge der benachbarten Elemente vertauscht.
       

Ausschlaggebend dafür, an welcher Position sich ein Objekt im sortierten Array einreiht, ist der paarweise Objektvergleich. Es stellt sich nun allerdings die Frage, nach welchen Kriterien Objekte vom Typ SortableObject verglichen werden sollen. Die statische Methode SortElements kann darüber keine Entscheidung treffen, da sie die typspezifischen Vergleichskriterien nicht kennt. Konsequenterweise muss der Vergleich in den von SortableObject abgeleiteten Klassen erfolgen. Dazu wird den ableitenden Klassen eine Methode vorgeschrieben, die als Ergebnis des Vergleichs zweier typgleicher Objekte einen booleschen Wert liefert. Wir nennen diese Methode CompareTo.

Damit jede Klasse, die SortableObject ableitet, die Methode CompareTo nach eigenen Maßstäben implementiert, wird CompareTo in der Klasse SortableObject abstrakt definiert. Damit sieht die endgültige Klassendefinition wie folgt aus:


Public MustInherit Class SortableObject
Public MustOverride Function CompareTo(ByVal a _
As SortableObject)_As Boolean
End Class

Der Rückgabewert soll True sein, wenn das Objekt, auf dem die Methode CompareTo aufgerufen wird, größer ist als das Objekt, das dem Parameter übergeben wird. In allen anderen Fällen sei der Rückgabewert False. Wie und nach welchen Gesichtspunkten der Vergleich erfolgt, entscheidet die Klasse, welche die abstrakte Methode CompareTo überschreibt. Natürlich können die booleschen Rückgabewerte auch vertauscht werden. Dann werden die Array-Elemente jedoch nicht auf-, sondern absteigend sortiert.

Mit diesen Vorgaben kann nun die Methode SortElements vollständig implementiert werden:


Public Class ArraySort
Public Shared Sub SortElements(ByRef arr() As SortableObject)
' n = Anzahl der Elemente
Dim n As Integer = arr.GetUpperBound(0) + 1
' Temp = temporäre Variable
Dim Temp As SortableObject
Dim i, k As Integer
For i = n – 1 To 1 Step –1
For k = 0 To i – 1
If arr(k + 1).CompareTo(arr(k)) Then
Temp = arr(k)
arr(k) = arr(k + 1)
arr(k + 1) = Temp
End If
Next
Next
End Sub
End Class

Resümieren wir an dieser Stelle, denn wir haben bereits alle Anforderungen erfüllt. Wir haben die abstrakte Klasse SortableObject entwickelt, welche die Methode CompareTo bereitstellt, um zwei Objekte miteinander zu vergleichen. Per Definition kann die Methode Com-pareTo nur Objekte vergleichen, deren Klassen die abstrakte Klasse SortableObject ableiten.

In der Klasse ArraySort ist eine Methode implementiert, die in der Lage ist, ein Array von SortableObject-Objekten der Größe nach zu sortieren. Aber warum müssen es gerade Objekte dieses Typs sein, warum nicht andere, beliebige Objekte, von denen beispielsweise einfach zwei Längenmaße miteinander verglichen werden? Die Antwort liefert ein Blick in die Implementierung der Sortierroutine SortElements. Der Entwickler der Klasse ArraySort kannte die abstrakte Klasse SortableObject. Er wusste, dass Klassen, welche die Klasse SortableObject ableiten, die Methode CompareTo bereitstellen. Auf diese Kenntnis wird in der Sortierroutine zurückgegriffen, wenn die CompareTo-Methode auf ein Objekt aufgerufen wird.

ArraySort und SortableObject seien in einer Klassenbibliothek implementiert.

Die ableitende Klasse

Versetzen wir uns in die Lage eines Benutzers, der eine Klasse namens LngNumber entwickelt, die unter anderem ein Feld vom Typ Long bereitstellt. Dieser Benutzer möchte sicherstellen, dass ein Objektarray vom Typ LngNumber der Größe nach sortiert werden kann.

Um sich die Mühe einer eigenen Implementierung zu sparen, recherchiert er in diversen Dokumentationen und stößt auf die Klasse ArraySort mit ihrer Methode SortElements, welche die Lösung seines Problems darstellt. Da beide Klassen voneinander abhängig sind, werden sich beide vermutlich sogar in derselben Klassenbibliothek befinden. Der Dokumentation entnimmt unser fiktiver Benutzer außerdem, dass er die Klasse ArraySort ableiten und deren abstrakte Methode CompareTo implementieren muss. Das Ergebnis könnte in den für uns entscheidenden Punkten wie folgt aussehen:


Public Class LngNumber
Inherits SortableObject
Private lngValue As Long
Public Sub New(ByVal lng As Long)
MyBase.new()
lngValue = lng
End Sub
Public Property Value() As Long
Get
Return lngValue
End Get
Set(ByVal Value As Int64)
lngValue = Value
End Set
End Property
Public Overrides Function CompareTo(ByVal b _
As SortableObject) As Boolean
If Value < CType(b, LngNumber).lngValue Then
Return True
Else
Return False
End If
End Function
End Class

Die Testanwendung

Was uns jetzt noch bleibt, ist die Bestätigung unserer Überlegungen durch eine Testanwendung:


' ----------------------------------------------------------
' Beispiel: ...\Kapitel 6\Sortierroutine1
' ----------------------------------------------------------
Module Module1
Sub Main()
Dim i As Integer
Dim arr(4) As LngNumber
arr(0) = New LngNumber(8)
arr(1) = New LngNumber(6)
arr(2) = New LngNumber(34)
arr(3) = New LngNumber(232)
arr(4) = New LngNumber(2)
'Aufruf der statischen Methode SortElements unter
'Übergabe des Objekt-Arrays
ArraySort.SortElements(arr)
'Ausgabe an der Konsole
For i = 0 To 4
Console.WriteLine("Element(" & i & ") = " & arr(i).Value)
Next
Console.ReadLine()
End Sub
End Module

Zunächst wird ein Array aus fünf Elementen vom Typ LngNumber deklariert, die im zweiten Schritt unter Übergabe der Initialisierungswerte an den Konstruktor konkretisiert werden. Die Array-Elemente liegen zunächst in unsortierter Reihenfolge vor und werden mit der Anweisung


ArraySort.SortElements(arr)

in die richtige Reihenfolge gebracht. Die Ausgabe an der Konsole wird für die Elemente des Arrays arr lauten:


Element(0) = 2
Element(1) = 6
Element(2) = 8
Element(3) = 34
Element(4) = 232

Sie sehen, dass die Sortierroutine ihre Aufgabe einwandfrei erledigt. Wenigstens haben sich unsere Mühen gelohnt, wenn der Weg auch ein wenig steinig war.

Die Lösung mit einer Schnittstellendefinition

Der Code des Beispiels aus dem vorhergehenden Abschnitt funktioniert tadellos. Aber ihm haftet ein wesentliches Problem an, das sehr häufig auftritt, wenn abstrakte Basisklassen abgeleitet werden: Die Common Language Runtime unterstützt keine Mehrfachvererbung, sondern erlaubt nur eine Basisklasse. Solange die Klasse LngNumber nicht aus einer anderen Basisklasse abgeleitet wird, ist der oben gezeigte Lösungsansatz akzeptabel. Sobald aber eine weitere Basisklasse ins Rampenlicht rückt, muss ein anderer Weg beschritten werden.

Genau an dieser Stelle greift das Konzept der Schnittstellen. Denn anstatt eine abstrakte Klasse zu beerben, werden die abstrakten Methoden über eine Schnittstelle offen gelegt. Damit wird ein Großteil der Funktionalität der Mehrfachvererbung wiedererlangt, ohne die damit verbundenen Nachteile in Kauf nehmen zu müssen. Da eine Klasse beliebig viele Schnittstellen implementieren darf, kann sie auch um die unterschiedlichsten Verhaltensweisen erweitert werden.

Damit wird aus der abstrakten Klasse Sortable eine Schnittstellendefinition, wie im Folgenden dargestellt:


' ----------------------------------------------------------
' Beispiel: ...\Kapitel 6\Sortierroutine2
' ----------------------------------------------------
Public Interface ISortableObject
Function CompareTo(ByVal a As ISortableObject) As Boolean
End Interface

Konventionsgemäß ergänzen wir den Schnittstellenbezeichner um das Präfix »I«. Die Änderung einer abstrakten Klasse in eine Schnittstelle wirkt sich weder auf die Definition der Klasse ArraySort noch auf die der Methode SortElements aus. Allerdings müssen die Klasse LngNumber und die aus der Schnittstelle übernommenen Methoden an die Schnittstellenimplementierung angepasst werden. Während die Ableitung einer abstrakten Klasse das Überschreiben der abstrakten Methoden mit dem Schlüsselwort Overrides erforderlich macht, ist dieses bei der Implementierung der Schnittstellen-Member in der implementierenden Klasse nicht zulässig.

Der folgende Codeausschnitt gibt die notwendigen Änderungen wieder.


Public Class LngNumber
Implements ISortableObject
Private lngValue As Long
Public Sub New(ByVal lng As Long)
MyBase.new()
lngValue = lng
End Sub
Public Property Value() As Long
Get
Return lngValue
End Get
Set(ByVal Value As Int64)
lngValue = Value
End Set
End Property
Public Function CompareTo(ByVal a _
As SortLibrary.ISortableObject) As Boolean _
Implements SortLibrary.ISortableObject.CompareTo
If Value < CType(a, LngNumber).lngValue Then
Return True
Else
Return False
End If
End Function
End Class

Damit ist die ursprünglich abstrakte Klasse durch eine Schnittstelle ersetzt, und der Code wird in gleicher Weise zum Ziel führen. Nicht anzuzweifeln ist die durch die Schnittstellendefinition gewonnene Flexibilität im Vergleich zur abstrakten Klasse, da eine Schnittstelle das möglicherweise unumgängliche Ableiten einer Basisklasse nicht blockiert. Daher sollten Schnittstellen immer dann bevorzugt eingesetzt werden, wenn die Implementierungsvererbung nicht unbedingt notwendig ist.


Galileo Computing

6.10.5 Zusammenfassung  toptop

gp  Schnittstellen sind die konsequente Fortsetzung der Idee einer abstrakten Klasse. Schnittstellen bieten einer Klasse ihre Dienste in Form von abstrakten Membern an, und der Nutzer verpflichtet sich, diese zu implementieren.
gp  Interfaces können nicht instanziiert werden, weil sie nur Verhaltensweisen festlegen, jedoch keine Daten bereitstellen.
gp  Eine Schnittstelle wird mit dem Schlüsselwort Interface und dem sich anschließenden Bezeichner definiert. Dem schließt sich ein Block abstrakter Definitionen an. Konventionsgemäß beginnt der Bezeichner einer Schnittstelle mit »I«.
gp  Im Gegensatz zu einer abstrakten Klasse definieren Schnittstellen ausschließlich abstrakte Member.
gp  Eine Klasse kann beliebig viele Schnittstellen implementieren. Implementiert eine Basisklasse eine Schnittstelle, wird die Schnittstelle an alle von dieser Basisklasse abgeleiteten Subklassen weitervererbt.
gp  Ein aus einer Schnittstelle übernommenes Mitglied darf nur Public sein. Es ist zulässig, in einer Klasse ein Schnittstellenmitglied MustOverride oder Overridable zu implementieren, es darf jedoch nicht Shared oder Const sein.
 <<   zurück
  
  Zum Katalog
Zum Katalog: Visual Basic 2005
Visual Basic 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Visual C# 2005






 Visual C# 2005


Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Das Programmierhandbuch SQL Server 2005






 Das Programmier-
 handbuch
 SQL Server 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de